# Merise python parser and models generators.
#
# @autor: Patrice Ferlet <metal3d@gmail.com>
# @version 0.0.2
# @licence Gnu/GPL v3

# $Id: graph.py 28 2010-04-26 08:59:03Z Metal3d $
# $HeadURL: http://merisier.googlecode.com/svn/tags/0.0.2-alpha1/src/merise/graph.py $

"""
Yield Graphs from parsed merise file
Graph are named as initial merise basename file suffixed by MCD or MPD
"""
import subprocess
import os, re

class Graph:
    """
    Graph class that can create 2 kinds of graphs
    MCD (Conceptual Graph Data Model)
    MPD (Phisical Graph Data Model)
    """
    def __init__(self, parser):
        """
        Initialize Graph from a given merise.parser object
        """
        self.parser = parser
        self.mcddot = "dot"
        self.mpddot ="dot"
        self.mcdrank = "LR"
        self.mpdrank = "LR"
        
        #see options
        self.title_color = self.parser.options.color
        self.front_title_color = self.parser.options.fcolor
        (self.basename, ext) = os.path.splitext(os.path.basename(self.parser.options.filename))
        print self.basename
        self.algo = self.parser.options.algo
        self.format = self.parser.options.imageformat
        self.fontname = self.parser.options.fontname
        #dot template
        self.template = """
            %s structs {
                rankdir="%s"
                node [shape=plaintext, fontname=%s]
                %s
                %s
                %s
            }
        """
    
    def __generateMCDDef(self):
        """
        We must to set every entities for MCD or MPD
        """
        struct = ""
        for k,v in self.parser.defs.items():
            _def = []
            _index = 0
            _def.append("<TR><TD BGCOLOR=\"%s\"><FONT COLOR=\"%s\" >%s</FONT></TD></TR>" % (self.title_color, self.front_title_color,k))
            for d in v:
                if d <> "":
                    if re.search('\+',d):
                        d = d.replace('+','')
                        _def.append("<TR><TD ALIGN=\"LEFT\" port=\"%s\">%s</TD></TR>" % (self.parser.getId(k), d))
                    else:
                        _def.append("<TR><TD ALIGN=\"LEFT\" port=\"%s_%s\">%s</TD></TR>" % (k,d, d))
                    _index+=1
            _def = "".join(_def)            
            _def ="label=<<TABLE ALIGN=\"LEFT\" CELLPADDING=\"5\" CELLSPACING=\"0\">%s</TABLE>>" % _def
            struct += "%s [%s];\n" % (k,_def)
            
        return struct
                
    def generateMCD(self):
        """
        Generate Conceptual Datas Model
        """
        rep = self.__generateMCDDef();
        nodes = []
        done = []
        index = 0
        _node = ""
        _link = ""
        for l in self.parser.links:
            (table1, num1, link, num2 , table2, attr) = l
            #set rounded link system
            if link not in nodes:
                _node +="%s [style=rounded, shape=\"record\", label=\"{ %s }\"];\n" % (link, link)
                if len(attr):
                    _attr = re.findall('\((.*)\)', attr)
                    _attr = _attr[0].split(',')
                    label = []
                    for a in _attr:
                        a=a.strip()
                        a = a.split(' ')
                        label.append(a[0])
                        
                    _node += "%s [shape=plaintext, label=\"%s\l\"]; \n" %  ("attr_"+link, "\l".join(label))
                    _link += "%s -- %s\n" % (link, "attr_"+link) 
                nodes.append(link)
            if table1+link not in done:
                _link += "%s -- %s [label=\"%s\", fontname=\"%s\"]\n" % (table1, link, num1, self.fontname)
                done.append(table1+link)
            if table2+link not in done:
                _link += "%s -- %s [label=\"%s\", fontname=\"%s\"]\n" % (link, table2, num2, self.fontname)
                done.append(table2+link)
            index+=1
            
        dot = self.template % ("graph", self.mcdrank,self.fontname, rep ,_node, _link)
        
        
        path = os.path.join( self.parser.options.directory, self.basename+"_MCD."+self.format )
        
        p = subprocess.Popen(args=[self.mcddot, '-T'+self.format, "-o",path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
        p.communicate(dot)
        p.wait()
        
        print path+" generated"
                        
    def generateMPD(self):
        """
        Generate Physical Data Model
        """
        self.parser.findCardinals()
        struct = self.__generateMCDDef()
        s = "";
        i=0
        for l in self.parser.arrows:
            (_from, _to) = l
            if self.seed(i):
                (table1, id_table1) = _from
                (table2, id_table2) = _to
                s+= "%s:%s:e -> %s:%s:w [arrowtail=\"dot\",arrowhead=\"normal\", fontname=\"%s\"]\n" % (table1, id_table1, table2, id_table2, self.fontname) 
            else:
                (table1, id_table1) = _to
                (table2, id_table2) = _from
                s+= "%s:%s:e -> %s:%s:w [arrowtail=\"normal\", arrowhead=\"dot\", fontname=\"%s\"]\n" % (table1, id_table1, table2, id_table2, self.fontname) 
            i+=1
        
        dot = self.template % ("digraph", self.mpdrank, self.fontname, struct ,s,"")

        path = os.path.join( self.parser.options.directory, self.basename+"_MPD."+self.format )
        
        p = subprocess.Popen(args=[self.mpddot,'-Etailclip=true','-Eheadclip=true' ,'-T'+self.format, "-o",path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True )
        p.communicate(dot)
        p.wait()
        
        print path+" generated"
        
    def seed(self,i):
        """
        Return true or false based on algorythm and given table index. 
        """
        if self.algo == "modulo":
            return i%(len(self.parser.arrows)+1/2) == 0
        elif self.algo == "divide":
            return i > len(self.parser.arrows)/2
        elif self.algo == "random":
            import random
            return random.randint(0,1)
        elif self.algo == "even":
            return i%2==0
        elif self.algo == "odd":
            return i%2!=0
        else:
            #none...
            return False
